Initial setting

Database part

Geo data and April data collections

# Select all tweets in April 2020
if(F){
paste("CREATE TABLE CoronavirusTweets",
      " AS SELECT * FROM CoronavirusTweetsCsv",
      " WHERE (strftime('%Y-%m-%d %H:%M:%S',created_at)>=",
      "strftime('%Y-%m-%d %H:%M:%S','2020-03-29 00:00:00'))",
      " AND (strftime('%Y-%m-%d %H:%M:%S',created_at)<=",
      "strftime('%Y-%m-%d %H:%M:%S','2020-04-29 23:59:59'))",sep='')%>%
  dbSendQuery(conn,.)
  
April_tweet=paste("SELECT Tweet_ID FROM CoronavirusTweets",sep='')%>%
  dbGetQuery(conn,.)
# Set Twitter developer account
create_token(app='MSSP-An-Auxiliary-Tool',
             consumer_key='ORvbA3CEOP06hi9MHfz7yknwV',
             consumer_secret='nAy2PRkiV4AYZ0NvHAF6Iw0IBFrttWMKTuxXbUWN4bcZnMpTQR',
             access_token='1328377313562509313-j1iSFuJLLo3FL768jdnHKe1fzmcWnS',
             access_secret='oJIhGoThBNSBSMLQBo3AxS5kcLrqq8sCjx6OIPX9NRmPT')
for(i in 1:ceiling(nrow(April_tweet)/90000)) {
  rl=rate_limit("lookup_statuses")
  if(rl%>%select(remaining)!=900){
    rl%>%select(reset)*60%>%ceiling()%>%Sys.sleep()
  }
  april_tweet=lookup_statuses(April_tweet$Tweet_ID[(900*i):nrow(April_tweet)])
  if(i==1){April_tweet=april_tweet}else{April_tweet=rbind(April_tweet,april_tweet)}
}
  April_tweet%>%
  select(status_id,user_id,screen_name,created_at,text,is_quote,
         is_retweet,favourites_count,retweet_count,followers_count,
         friends_count,lang)%>%
  dbWriteTable(conn,'CoronavirusTweets',.)
}
# Select all tweets with geo information from 202001 to 202011
if(F){
paste("CREATE TABLE CoronavirusTweetsGeo",
      " AS SELECT * FROM CoronavirusTweetsCsv",
      " WHERE Geolocation_coordinate='YES'",sep='')%>%
  dbSendQuery(conn,.)

Geo_tweet=paste("SELECT Tweet_ID FROM CoronavirusTweetsGeo",sep='')%>%
  dbGetQuery(conn,.)


for(i in 1:ceiling(nrow(Geo_tweet)/90000)) {
  rl=rate_limit("lookup_statuses")
  if(rl%>%select(remaining)!=900){
    rl%>%select(reset)*60%>%ceiling()%>%Sys.sleep()
  }
  geo=lookup_statuses(Geo_tweet$Tweet_ID[(900*i):nrow(Geo_tweet)])
  if(i==1){Geo=geo}else{Geo=rbind(Geo,geo)}
}

lat_lng(Geo)%>%
  select(status_id,user_id,screen_name,created_at,text,is_quote,
         is_retweet,favourites_count,retweet_count,followers_count,
         friends_count,lang,place_full_name,place_type,country_code,
         place_name,country,lat,lng)%>%
  dbWriteTable(conn,'CoronavirusTweetsGeo',.)

# Delete initial collection of covid tweets csv files table
"DROP TABLE CoronavirusTweetsCsv" %>%
  dbSendQuery(conn,.)
# Create index to accelerate query
paste("CREATE INDEX CT_status_id ON CoronavirusTweets(status_id);",
  "CREATE INDEX CTG_status_id ON CoronavirusTweetsGeo(status_id);",
  "CREATE INDEX TS_status_id ON TweetsSentiment(status_id);",
  "CREATE INDEX TGS_status_id ON TweetsGeoSentiment(status_id);",
  "CREATE INDEX CTG_lat_long ON CoronavirusTweetsGeo(lat,lng);",
  "CREATE UNIQUE INDEX GD_lat_long ON GeoDetail(lat,lng)")%>%
  dbSendQuery(conn,.)
}
dbDisconnect(conn)

Get data function

getTwitterData=function(conn,geoinfo=T,keywords=NULL,
                        period=c('2020-03-29 00:00:00','2020-04-30 23:59:59')){
  # Select table of database according to 'geoinfo'
  if(geoinfo){
    geoinfo_query=paste("SELECT CoronavirusTweetsGeo.*,",
                        "city,state,country,sentiment_score ",
                        "FROM CoronavirusTweetsGeo ",
                        "LEFT JOIN TweetsGeoSentiment ON ",
                        "CoronavirusTweetsGeo.status_id=",
                        "TweetsGeoSentiment.status_id ",
                        "LEFT JOIN GeoDetail ON ",
                        "CoronavirusTweetsGeo.lat=GeoDetail.lat ",
                        "AND CoronavirusTweetsGeo.lng=GeoDetail.lng",sep="")
  }
  else{
    geoinfo_query=paste("SELECT CoronavirusTweets.*,sentiment_score ",
                        "FROM CoronavirusTweets ",
                        "LEFT JOIN TweetsSentiment ON ",
                        "CoronavirusTweets.status_id=",
                        "TweetsSentiment.status_id",sep="")
  }
  # Add keywords conditions according to 'keywords' 
  if(length(keywords==0)){
    keywords_query=''
  }
  else{
    for(i in 1:length(keywords)){
      if(i==1){
        keywords_query=paste(" ((text LIKE '%",keywords[i],"%')",sep="")
      }
      else{
        keywords_query=keywords_query%>%
          paste("OR (text LIKE '%",keywords[i],"%')",sep="")
      }
    }
    keywords_query=paste(keywords_query,") ",sep="")
  }
  # Add period conditions according to 'period'
  if(length(period)!=2){
    period_query=''
  }
  else{
    period_query=paste(" (strftime('%Y-%m-%d %H:%M:%S',created_at)>=",
                       "strftime('%Y-%m-%d %H:%M:%S','",period[1],"') ",
                       "AND strftime('%Y-%m-%d %H:%M:%S',created_at)<=",
                       "strftime('%Y-%m-%d %H:%M:%S','",period[2],"')) ",
                       sep="")
  }
  # Write SQL
  if(period_query==''){
    if(keywords_query==''){
      query=paste(geoinfo_query,sep="")
    }
    else{
      query=paste(geoinfo_query," WHERE",keywords_query,sep="")
    }
  }
  else{
    if(keywords_query==''){
      query=paste(geoinfo_query," WHERE",period_query,sep="")
    }
    else{
      query=paste(geoinfo_query," WHERE",
                  period_query,"AND",keywords_query,sep="")
    }
  }
  # Obtain Data
 dbGetQuery(conn,query)
}

Get trend function

getTwitterTrend=function(conn,geoinfo='country',trend='day',keywords=NULL,
                       period=c('2020-03-29 00:00:00','2020-04-30 23:59:59')){
  # Add trend cconditions according to 'trend'
  if(trend=='day'){
    trend_query=c("'%Y-%m-%d'","date")
  }
  else{
    if(trend=='week'){
      trend_query=c("'%W'","week")
    }
    else{
      if(trend=='month'){
        trend_query=c("'%m'","month")
      }
      else{
        stop("The trend can only be 'day', 'week' or 'month'.") 
      }
    }
  }
    # Select table of database according to 'geoinfo'
  if(is.null(geoinfo)){
    geoinfo_query=paste("SELECT strftime(",trend_query[1],
                        ",created_at) AS ",trend_query[2],", ",
                        "count(*) AS number, ",
                        "avg(sentiment_score) AS sentiment_score ",
                        "FROM CoronavirusTweets ",
                        "LEFT JOIN TweetsSentiment ON ",
                        "CoronavirusTweets.status_id=",
                        "TweetsSentiment.status_id",sep="")
    group_query=paste(" GROUP BY strftime(",trend_query[1],
                      ",created_at)",sep="")
  }
  else{
    if(geoinfo=='country'){
    geoinfo_query=paste("SELECT strftime(",trend_query[1],
                        ",created_at) AS ",trend_query[2],", ",
                        "count(*) AS number, country, ",
                        "avg(sentiment_score) AS sentiment_score ",
                        "FROM CoronavirusTweetsGeo ",
                        "LEFT JOIN TweetsGeoSentiment ON ",
                        "CoronavirusTweetsGeo.status_id=",
                        "TweetsGeoSentiment.status_id ",
                        "LEFT JOIN GeoDetail ON ",
                        "CoronavirusTweetsGeo.lat=GeoDetail.lat ",
                        "AND CoronavirusTweetsGeo.lng=GeoDetail.lng",sep="")
    group_query=paste(" GROUP BY strftime(",trend_query[1],
                      ",created_at),country",sep="")
    }
    else{
      if(geoinfo=='state'){
        geoinfo_query=paste("SELECT strftime(",trend_query[1],
                            ",created_at) AS ",trend_query[2],", ",
                            "count(*) AS number, country, state, ",
                            "avg(sentiment_score) AS sentiment_score ",
                            "FROM CoronavirusTweetsGeo ",
                            "LEFT JOIN TweetsGeoSentiment ON ",
                            "CoronavirusTweetsGeo.status_id=",
                            "TweetsGeoSentiment.status_id ",
                            "LEFT JOIN GeoDetail ON ",
                            "CoronavirusTweetsGeo.lat=GeoDetail.lat ",
                            "AND CoronavirusTweetsGeo.lng=GeoDetail.lng",sep="")
        group_query=paste(" GROUP BY strftime(",trend_query[1],
                          ",created_at),country,state",sep="")
      }
      else{
        if(geoinfo=='city'){
          geoinfo_query=paste("SELECT strftime(",trend_query[1],
                              ",created_at) AS ",trend_query[2],", ",
                              "count(*) AS number, country, state, city, ",
                              "avg(sentiment_score) AS sentiment_score ",
                              "FROM CoronavirusTweetsGeo ",
                              "LEFT JOIN TweetsGeoSentiment ON ",
                              "CoronavirusTweetsGeo.status_id=",
                              "TweetsGeoSentiment.status_id ",
                              "LEFT JOIN GeoDetail ON ",
                              "CoronavirusTweetsGeo.lat=GeoDetail.lat ",
                              "AND CoronavirusTweetsGeo.lng=GeoDetail.lng",
                              sep="")
           group_query=paste(" GROUP BY strftime(",trend_query[1],
                             ",created_at),country,state,city",sep="")
        }
        else{
          stop("The geoinfo can only be 'NULL', 'city', 'state' or 'country'.")
        }
      }
    }
  }
  
  # Add keywords conditions according to 'keywords' 
  if(is.null(keywords)){
    keywords_query=''
  }
  else{
    for(i in 1:length(keywords)){
      if(i==1){
        keywords_query=paste(" ((text LIKE '%",keywords[i],"%')",sep="")
      }
      else{
        keywords_query=keywords_query%>%
          paste("OR (text LIKE '%",keywords[i],"%')",sep="")
      }
    }
    keywords_query=paste(keywords_query,") ",sep="")
  }
  # Add period conditions according to 'period'
  if(is.null(period)){
    period_query=''
  }
  else{
    if(length(period)==2){
          period_query=paste(" (strftime('%Y-%m-%d %H:%M:%S',created_at)>=",
                       "strftime('%Y-%m-%d %H:%M:%S','",period[1],"') ",
                       "AND strftime('%Y-%m-%d %H:%M:%S',created_at)<=",
                       "strftime('%Y-%m-%d %H:%M:%S','",period[2],"')) ",
                       sep="")
    }
    else{
      stop("The time period should be a vector with length 2.") 
    }
  }
  # Write SQL
  if(period_query==''){
    if(keywords_query==''){
      query=paste(geoinfo_query,group_query,sep="")
    }
    else{
      query=paste(geoinfo_query," WHERE",keywords_query,group_query,sep="")
    }
  }
  else{
    if(keywords_query==''){
      query=paste(geoinfo_query," WHERE",period_query,group_query,sep="")
    }
    else{
      query=paste(geoinfo_query," WHERE",period_query,"AND",keywords_query,
                  group_query,sep="")
    }
  }
  # Obtain Data
 dbGetQuery(conn,query)
}

Examples

# connect to data base
conn=dbConnect(SQLite(),dbpath)
# get twitter data with geo information
tweetsGeo=getTwitterData(conn,geoinfo = T,period = NULL)
# get twitter montly data with geo information
tweetsMonthlyGeo=getTwitterTrend(conn,geoinfo = 'country',trend='month',period=NULL)
# get twitter data with giving keywords
tweets=getTwitterData(conn,geoinfo = F,keywords = c('mask','N95','口罩'))
# get twitter daily trends giving keywords
tweetsDaily=getTwitterTrend(conn,geoinfo = NULL,keywords = c('mask','N95','口罩'))

# disconnect data base
dbDisconnect(conn)
# Release memory
rm(tweetsGeo,tweetsMonthlyGeo,tweets,tweetsDaily)
gc()

The sentiment analysis part

# For English tweets in United States
getEnScore <- function(DF, keyword){
  DFsub <- DF %>%
    filter(lang=="en"&country_code=="US")
  
  DFsub$date <- gsub("T.*", "", DFsub$created_at)
  
  wordDF <- DFsub %>%
    unnest_tokens(word, text) %>%
    anti_join(stop_words) 
  
  scoreDF <- wordDF %>%
    inner_join(get_sentiments("bing")) %>%
    count(status_id, sentiment) %>%
    spread(sentiment, n)
  scoreDF[is.na(scoreDF)] <- 0
  scoreDF <- scoreDF %>%
    mutate(sentiment_score=(positive-negative)/(positive+negative)) 
  
  tweetScoreDF <- right_join(DFsub, scoreDF, by="status_id")
  
  if(length(keyword)!=1){
    keyword <- paste(keyword, collapse="|")
  }
  keywordDF <- tweetScoreDF %>%
    filter(grepl(keyword, text))
  
  return(
    keywordDF %>%
      group_by(date) %>%
      summarize(overall_sentiment=mean(sentiment_score))
  )
}
# For all languages
# Create sentiment lexicon dictionary
# https://www.kaggle.com/rtatman/sentiment-lexicons-for-81-languages

langCode <- read.csv("SentimentLexicons/correctedMetadata.csv", header=TRUE)$`Wikipedia.Language.Code`
cannot open file 'SentimentLexicons/correctedMetadata.csv': No such file or directoryError in file(file, "rt") : cannot open the connection

Visualization


# load spread data
daily <- read.csv(file= "/Users/mac/Desktop/Trinity/us_covid19_daily.csv", header=TRUE)
spread_data <- select(daily,date,hospitalizedCumulative,death,deathIncrease,negativeIncrease,positiveIncrease)%>%
  mutate(date_new=ymd(date))%>%
  arrange(daily, desc(date_new))
spread_data <- spread_data[68:100,]


# using plotly package to make a plot that both have death, sentiment and frequency
positiveincrease <- spread_data[,6]
frequency <- mask$number
sentiment <- mask$sentiment_score
date <-spread_data$date_new
data <- data.frame(date, positiveincrease, frequency, sentiment)

ay <- list(
  tickfont = list(color = "blue"),
  overlaying = "y",
  side = "right",
  title = "the frequency of "
)

fig <- plot_ly(data, x = ~date, y = ~positiveincrease, name = 'positiveincrease', type = 'scatter', mode = 'lines+markers') 
for(i in 1:32)
  if((sentiment[i]+sentiment[i+1])/2 >0 ){
    fig <-fig %>% add_trace(y = frequency[i:(i+1)], x=date[i:(i+1)], name = NULL,  mode = 'lines',yaxis = "y2",color = I("green"))
  }else{
              
    fig <-fig %>% add_trace(y = frequency[i:(i+1)], x=date[i:(i+1)], name = NULL,  mode = 'lines',yaxis = "y2",color = I("red"))       
    }
              
fig <- fig %>% layout(
  title = "Statistics about 'mask, N95'", yaxis2 = ay,
  xaxis = list(title="x"))
fig

NA

Geom plots


# For the geo plot
#states <- c("texas","oklahoma","kansas","louisiana","arkansas","missouri","iowa",
#"wisconsin","michigan","illinois","indiana","ohio","kentucky","tennessee",
#"alabama","mississippi","florida","georgia","south carolina","north carolina",
#"virginia","west virginia","maryland","delaware","pennsylvania","new jersey",
#"new york","connecticut","rhode island","massachusetts","vermont",
#"new hampshire","maine")

#turn data from the maps package in to a data frame suitable for plotting with ggplot2
#map_states <- map_data("county", states)
# To draw the border-by group 10
#map_states_border <- map_data("state",states)
tweetsGeo <- tweetsGeo %>%
  group_by(state)%>%
  mutate(sum = n(),
         long=lng)


tweetsGeo_En <- filter(tweetsGeo, country == 'United States')
View(tweetsGeo_En)
# summary(tweetsGeo_En$sum)
# divide sum of tweets in each state into 4 parts which is 
# tweetsGeo_En$cut <- cut(tweetsGeo_En$sum,
#                      breaks=c(0,1658,7890,15064,18206),
 #                     include.lowest = T)

tweetsGeo_En_1 <- tweetsGeo_En%>%
  mutate(long=as.double(long),
         lat=as.double(lat))%>%
  group_by(state)%>%
  summarize(sum_states=n(), mean_sentiment=mean(sentiment_score))
`summarise()` ungrouping output (override with `.groups` argument)
# Make the geo plot in ggplot
#tweetsGeo_plot <- ggplot()+
#  geom_polygon(tweetsGeo_En_1, mapping=aes(x=long, y=lat, group=city), fill=cut)+
 # connects the observations in the order in which they appear in the 'map_states'
#  geom_path(map_states, mapping=aes(x=long, y=lat,group=city),color="grey")+
 #  Add the border to make it clear-by Group 10
#  geom_path(map_states_border, mapping=aes(x=long, y=lat,group=city),color="black")+
  
 #  display discrete values on a map
#  scale_fill_brewer(palette="Blues")+
  # change the name of x, y, and title
#  xlab("Longtitude")+ylab("Latitude")+ggtitle("tweetsGeo")+
 #  add marks
#  labs(fill="number of tweets")+
#  theme(plot.title = element_text(hjust = 0.5, size = 18))

# tweetsGeo_plot


Apr40 <- read.csv(file= "/Users/mac/Desktop/Trinity/us_states_daily.csv", header=TRUE)
Geoplot <- merge(Apr40,tweetsGeo_En_1 , by=c("state"))
Geoplot$hover <- with(Geoplot, paste(state, '<br>',  '<br>', "Positive:", positive, "<br>","death:", death,"<br>", "number of tweets", sum_states,'<br>', "sentiment score of this state", mean_sentiment)) #put data

g <- list(
  scope = 'usa',
  projection = list(type = 'albers usa'),
  showlakes = TRUE,
  lakecolor = toRGB('white')
)
fig <- plot_geo(Geoplot, locationmode = 'USA-states') 
fig <- fig %>% add_trace(
  locations = ~state,
  type='choropleth',
  z= ~mean_sentiment,
  text = ~hover,
  colors="Reds"
)
# Add a title
fig <- fig %>% layout(title = "sentiment score of each state",geo = g)
# Final
fig

NA
g <- list(
  scope = 'usa',
  projection = list(type = 'albers usa'),
  showlakes = TRUE,
  lakecolor = toRGB('white'))
fig <- plot_geo(Geoplot, locationmode = 'USA-states') 
fig <- fig %>% add_trace(
  locations = ~state,
  type='choropleth',
  z= ~death,
  text = ~hover,
  colors="Purples"
)
# Add a title
fig <- fig %>% layout(title = "sentiment score of each state",geo=g)
# Final
fig
LS0tCnRpdGxlOiAiQ292aWQtVHdlZXRzIgphdXRob3I6ICJHcm91cDI6RGFucGluZyBMaXUsIEhhbyBTaGVuLCBIYW9xaSBXYW5nLCBZdXhpIFdhbmciCmRhdGU6ICIyMDIwLzEyLzIiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCiMjIEluaXRpYWwgc2V0dGluZwoqIFRoZSBkYXRhYmFzZSBjYW4gYmUgZG93bmxvYW5kIGZyb20gW09uZURyaXZlXShodHRwczovLzFkcnYubXMvdS9zIUF0b0EtUk15THBmMmhPMXJPODJwUDJ5OE9NZmctZz9lPWF6Zko0NCkKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkKcGFjbWFuOjpwX2xvYWQodGlkeXZlcnNlLERCSSxSU1FMaXRlLGx1YnJpZGF0ZSxydHdlZXQsdGlkeXRleHQsbHVicmlkYXRlLHJqc29uLHBsb3RseSxwYWNtYW4sbWFwcyx0bWFwLHNwLHJldmdlbyxEVAogICAgICAgICkKZGJwYXRoPSIvVXNlcnMvbWFjL0Rlc2t0b3AvQ292aWQtdHdlZXRzLWVuLmRiIgpjb25uPWRiQ29ubmVjdChTUUxpdGUoKSxkYnBhdGgpCmBgYAoKIyMgRGF0YWJhc2UgcGFydAojIyBHZW8gZGF0YSBhbmQgQXByaWwgZGF0YSBjb2xsZWN0aW9ucwoqIE5vdGU6IFRoaXMgY2h1bmsgbmVlZG4ndCBydW4gYWdhaW4uCmBgYHtyfQojIFNlbGVjdCBhbGwgdHdlZXRzIGluIEFwcmlsIDIwMjAKaWYoRil7CnBhc3RlKCJDUkVBVEUgVEFCTEUgQ29yb25hdmlydXNUd2VldHMiLAogICAgICAiIEFTIFNFTEVDVCAqIEZST00gQ29yb25hdmlydXNUd2VldHNDc3YiLAogICAgICAiIFdIRVJFIChzdHJmdGltZSgnJVktJW0tJWQgJUg6JU06JVMnLGNyZWF0ZWRfYXQpPj0iLAogICAgICAic3RyZnRpbWUoJyVZLSVtLSVkICVIOiVNOiVTJywnMjAyMC0wMy0yOSAwMDowMDowMCcpKSIsCiAgICAgICIgQU5EIChzdHJmdGltZSgnJVktJW0tJWQgJUg6JU06JVMnLGNyZWF0ZWRfYXQpPD0iLAogICAgICAic3RyZnRpbWUoJyVZLSVtLSVkICVIOiVNOiVTJywnMjAyMC0wNC0yOSAyMzo1OTo1OScpKSIsc2VwPScnKSU+JQogIGRiU2VuZFF1ZXJ5KGNvbm4sLikKICAKQXByaWxfdHdlZXQ9cGFzdGUoIlNFTEVDVCBUd2VldF9JRCBGUk9NIENvcm9uYXZpcnVzVHdlZXRzIixzZXA9JycpJT4lCiAgZGJHZXRRdWVyeShjb25uLC4pCiMgU2V0IFR3aXR0ZXIgZGV2ZWxvcGVyIGFjY291bnQKY3JlYXRlX3Rva2VuKGFwcD0nTVNTUC1Bbi1BdXhpbGlhcnktVG9vbCcsCiAgICAgICAgICAgICBjb25zdW1lcl9rZXk9J09SdmJBM0NFT1AwNmhpOU1IZno3eWtud1YnLAogICAgICAgICAgICAgY29uc3VtZXJfc2VjcmV0PSduQXkyUFJraVY0QVlaME52SEFGNkl3MElCRnJ0dFdNS1R1eFhiVVdONGJjWm5NcFRRUicsCiAgICAgICAgICAgICBhY2Nlc3NfdG9rZW49JzEzMjgzNzczMTM1NjI1MDkzMTMtajFpU0Z1SkxMbzNGTDc2OGpkbkhLZTFmem1jV25TJywKICAgICAgICAgICAgIGFjY2Vzc19zZWNyZXQ9J29KSWhHb1RoQk5TQlNNTFFCbzNBeFM1a2NMcnFxOHNDang2T0lQWDlOUm1QVCcpCmZvcihpIGluIDE6Y2VpbGluZyhucm93KEFwcmlsX3R3ZWV0KS85MDAwMCkpIHsKICBybD1yYXRlX2xpbWl0KCJsb29rdXBfc3RhdHVzZXMiKQogIGlmKHJsJT4lc2VsZWN0KHJlbWFpbmluZykhPTkwMCl7CiAgICBybCU+JXNlbGVjdChyZXNldCkqNjAlPiVjZWlsaW5nKCklPiVTeXMuc2xlZXAoKQogIH0KICBhcHJpbF90d2VldD1sb29rdXBfc3RhdHVzZXMoQXByaWxfdHdlZXQkVHdlZXRfSURbKDkwMCppKTpucm93KEFwcmlsX3R3ZWV0KV0pCiAgaWYoaT09MSl7QXByaWxfdHdlZXQ9YXByaWxfdHdlZXR9ZWxzZXtBcHJpbF90d2VldD1yYmluZChBcHJpbF90d2VldCxhcHJpbF90d2VldCl9Cn0KICBBcHJpbF90d2VldCU+JQogIHNlbGVjdChzdGF0dXNfaWQsdXNlcl9pZCxzY3JlZW5fbmFtZSxjcmVhdGVkX2F0LHRleHQsaXNfcXVvdGUsCiAgICAgICAgIGlzX3JldHdlZXQsZmF2b3VyaXRlc19jb3VudCxyZXR3ZWV0X2NvdW50LGZvbGxvd2Vyc19jb3VudCwKICAgICAgICAgZnJpZW5kc19jb3VudCxsYW5nKSU+JQogIGRiV3JpdGVUYWJsZShjb25uLCdDb3JvbmF2aXJ1c1R3ZWV0cycsLikKfQojIFNlbGVjdCBhbGwgdHdlZXRzIHdpdGggZ2VvIGluZm9ybWF0aW9uIGZyb20gMjAyMDAxIHRvIDIwMjAxMQppZihGKXsKcGFzdGUoIkNSRUFURSBUQUJMRSBDb3JvbmF2aXJ1c1R3ZWV0c0dlbyIsCiAgICAgICIgQVMgU0VMRUNUICogRlJPTSBDb3JvbmF2aXJ1c1R3ZWV0c0NzdiIsCiAgICAgICIgV0hFUkUgR2VvbG9jYXRpb25fY29vcmRpbmF0ZT0nWUVTJyIsc2VwPScnKSU+JQogIGRiU2VuZFF1ZXJ5KGNvbm4sLikKCkdlb190d2VldD1wYXN0ZSgiU0VMRUNUIFR3ZWV0X0lEIEZST00gQ29yb25hdmlydXNUd2VldHNHZW8iLHNlcD0nJyklPiUKICBkYkdldFF1ZXJ5KGNvbm4sLikKCgpmb3IoaSBpbiAxOmNlaWxpbmcobnJvdyhHZW9fdHdlZXQpLzkwMDAwKSkgewogIHJsPXJhdGVfbGltaXQoImxvb2t1cF9zdGF0dXNlcyIpCiAgaWYocmwlPiVzZWxlY3QocmVtYWluaW5nKSE9OTAwKXsKICAgIHJsJT4lc2VsZWN0KHJlc2V0KSo2MCU+JWNlaWxpbmcoKSU+JVN5cy5zbGVlcCgpCiAgfQogIGdlbz1sb29rdXBfc3RhdHVzZXMoR2VvX3R3ZWV0JFR3ZWV0X0lEWyg5MDAqaSk6bnJvdyhHZW9fdHdlZXQpXSkKICBpZihpPT0xKXtHZW89Z2VvfWVsc2V7R2VvPXJiaW5kKEdlbyxnZW8pfQp9CgpsYXRfbG5nKEdlbyklPiUKICBzZWxlY3Qoc3RhdHVzX2lkLHVzZXJfaWQsc2NyZWVuX25hbWUsY3JlYXRlZF9hdCx0ZXh0LGlzX3F1b3RlLAogICAgICAgICBpc19yZXR3ZWV0LGZhdm91cml0ZXNfY291bnQscmV0d2VldF9jb3VudCxmb2xsb3dlcnNfY291bnQsCiAgICAgICAgIGZyaWVuZHNfY291bnQsbGFuZyxwbGFjZV9mdWxsX25hbWUscGxhY2VfdHlwZSxjb3VudHJ5X2NvZGUsCiAgICAgICAgIHBsYWNlX25hbWUsY291bnRyeSxsYXQsbG5nKSU+JQogIGRiV3JpdGVUYWJsZShjb25uLCdDb3JvbmF2aXJ1c1R3ZWV0c0dlbycsLikKCiMgRGVsZXRlIGluaXRpYWwgY29sbGVjdGlvbiBvZiBjb3ZpZCB0d2VldHMgY3N2IGZpbGVzIHRhYmxlCiJEUk9QIFRBQkxFIENvcm9uYXZpcnVzVHdlZXRzQ3N2IiAlPiUKICBkYlNlbmRRdWVyeShjb25uLC4pCiMgQ3JlYXRlIGluZGV4IHRvIGFjY2VsZXJhdGUgcXVlcnkKcGFzdGUoIkNSRUFURSBJTkRFWCBDVF9zdGF0dXNfaWQgT04gQ29yb25hdmlydXNUd2VldHMoc3RhdHVzX2lkKTsiLAogICJDUkVBVEUgSU5ERVggQ1RHX3N0YXR1c19pZCBPTiBDb3JvbmF2aXJ1c1R3ZWV0c0dlbyhzdGF0dXNfaWQpOyIsCiAgIkNSRUFURSBJTkRFWCBUU19zdGF0dXNfaWQgT04gVHdlZXRzU2VudGltZW50KHN0YXR1c19pZCk7IiwKICAiQ1JFQVRFIElOREVYIFRHU19zdGF0dXNfaWQgT04gVHdlZXRzR2VvU2VudGltZW50KHN0YXR1c19pZCk7IiwKICAiQ1JFQVRFIElOREVYIENUR19sYXRfbG9uZyBPTiBDb3JvbmF2aXJ1c1R3ZWV0c0dlbyhsYXQsbG5nKTsiLAogICJDUkVBVEUgVU5JUVVFIElOREVYIEdEX2xhdF9sb25nIE9OIEdlb0RldGFpbChsYXQsbG5nKSIpJT4lCiAgZGJTZW5kUXVlcnkoY29ubiwuKQp9CmRiRGlzY29ubmVjdChjb25uKQpgYGAKCiMjIEdldCBkYXRhIGZ1bmN0aW9uCmBgYHtyfQpnZXRUd2l0dGVyRGF0YT1mdW5jdGlvbihjb25uLGdlb2luZm89VCxrZXl3b3Jkcz1OVUxMLAogICAgICAgICAgICAgICAgICAgICAgICBwZXJpb2Q9YygnMjAyMC0wMy0yOSAwMDowMDowMCcsJzIwMjAtMDQtMzAgMjM6NTk6NTknKSl7CiAgIyBTZWxlY3QgdGFibGUgb2YgZGF0YWJhc2UgYWNjb3JkaW5nIHRvICdnZW9pbmZvJwogIGlmKGdlb2luZm8pewogICAgZ2VvaW5mb19xdWVyeT1wYXN0ZSgiU0VMRUNUIENvcm9uYXZpcnVzVHdlZXRzR2VvLiosIiwKICAgICAgICAgICAgICAgICAgICAgICAgImNpdHksc3RhdGUsY291bnRyeSxzZW50aW1lbnRfc2NvcmUgIiwKICAgICAgICAgICAgICAgICAgICAgICAgIkZST00gQ29yb25hdmlydXNUd2VldHNHZW8gIiwKICAgICAgICAgICAgICAgICAgICAgICAgIkxFRlQgSk9JTiBUd2VldHNHZW9TZW50aW1lbnQgT04gIiwKICAgICAgICAgICAgICAgICAgICAgICAgIkNvcm9uYXZpcnVzVHdlZXRzR2VvLnN0YXR1c19pZD0iLAogICAgICAgICAgICAgICAgICAgICAgICAiVHdlZXRzR2VvU2VudGltZW50LnN0YXR1c19pZCAiLAogICAgICAgICAgICAgICAgICAgICAgICAiTEVGVCBKT0lOIEdlb0RldGFpbCBPTiAiLAogICAgICAgICAgICAgICAgICAgICAgICAiQ29yb25hdmlydXNUd2VldHNHZW8ubGF0PUdlb0RldGFpbC5sYXQgIiwKICAgICAgICAgICAgICAgICAgICAgICAgIkFORCBDb3JvbmF2aXJ1c1R3ZWV0c0dlby5sbmc9R2VvRGV0YWlsLmxuZyIsc2VwPSIiKQogIH0KICBlbHNlewogICAgZ2VvaW5mb19xdWVyeT1wYXN0ZSgiU0VMRUNUIENvcm9uYXZpcnVzVHdlZXRzLiosc2VudGltZW50X3Njb3JlICIsCiAgICAgICAgICAgICAgICAgICAgICAgICJGUk9NIENvcm9uYXZpcnVzVHdlZXRzICIsCiAgICAgICAgICAgICAgICAgICAgICAgICJMRUZUIEpPSU4gVHdlZXRzU2VudGltZW50IE9OICIsCiAgICAgICAgICAgICAgICAgICAgICAgICJDb3JvbmF2aXJ1c1R3ZWV0cy5zdGF0dXNfaWQ9IiwKICAgICAgICAgICAgICAgICAgICAgICAgIlR3ZWV0c1NlbnRpbWVudC5zdGF0dXNfaWQiLHNlcD0iIikKICB9CiAgIyBBZGQga2V5d29yZHMgY29uZGl0aW9ucyBhY2NvcmRpbmcgdG8gJ2tleXdvcmRzJyAKICBpZihsZW5ndGgoa2V5d29yZHM9PTApKXsKICAgIGtleXdvcmRzX3F1ZXJ5PScnCiAgfQogIGVsc2V7CiAgICBmb3IoaSBpbiAxOmxlbmd0aChrZXl3b3JkcykpewogICAgICBpZihpPT0xKXsKICAgICAgICBrZXl3b3Jkc19xdWVyeT1wYXN0ZSgiICgodGV4dCBMSUtFICclIixrZXl3b3Jkc1tpXSwiJScpIixzZXA9IiIpCiAgICAgIH0KICAgICAgZWxzZXsKICAgICAgICBrZXl3b3Jkc19xdWVyeT1rZXl3b3Jkc19xdWVyeSU+JQogICAgICAgICAgcGFzdGUoIk9SICh0ZXh0IExJS0UgJyUiLGtleXdvcmRzW2ldLCIlJykiLHNlcD0iIikKICAgICAgfQogICAgfQogICAga2V5d29yZHNfcXVlcnk9cGFzdGUoa2V5d29yZHNfcXVlcnksIikgIixzZXA9IiIpCiAgfQogICMgQWRkIHBlcmlvZCBjb25kaXRpb25zIGFjY29yZGluZyB0byAncGVyaW9kJwogIGlmKGxlbmd0aChwZXJpb2QpIT0yKXsKICAgIHBlcmlvZF9xdWVyeT0nJwogIH0KICBlbHNlewogICAgcGVyaW9kX3F1ZXJ5PXBhc3RlKCIgKHN0cmZ0aW1lKCclWS0lbS0lZCAlSDolTTolUycsY3JlYXRlZF9hdCk+PSIsCiAgICAgICAgICAgICAgICAgICAgICAgInN0cmZ0aW1lKCclWS0lbS0lZCAlSDolTTolUycsJyIscGVyaW9kWzFdLCInKSAiLAogICAgICAgICAgICAgICAgICAgICAgICJBTkQgc3RyZnRpbWUoJyVZLSVtLSVkICVIOiVNOiVTJyxjcmVhdGVkX2F0KTw9IiwKICAgICAgICAgICAgICAgICAgICAgICAic3RyZnRpbWUoJyVZLSVtLSVkICVIOiVNOiVTJywnIixwZXJpb2RbMl0sIicpKSAiLAogICAgICAgICAgICAgICAgICAgICAgIHNlcD0iIikKICB9CiAgIyBXcml0ZSBTUUwKICBpZihwZXJpb2RfcXVlcnk9PScnKXsKICAgIGlmKGtleXdvcmRzX3F1ZXJ5PT0nJyl7CiAgICAgIHF1ZXJ5PXBhc3RlKGdlb2luZm9fcXVlcnksc2VwPSIiKQogICAgfQogICAgZWxzZXsKICAgICAgcXVlcnk9cGFzdGUoZ2VvaW5mb19xdWVyeSwiIFdIRVJFIixrZXl3b3Jkc19xdWVyeSxzZXA9IiIpCiAgICB9CiAgfQogIGVsc2V7CiAgICBpZihrZXl3b3Jkc19xdWVyeT09JycpewogICAgICBxdWVyeT1wYXN0ZShnZW9pbmZvX3F1ZXJ5LCIgV0hFUkUiLHBlcmlvZF9xdWVyeSxzZXA9IiIpCiAgICB9CiAgICBlbHNlewogICAgICBxdWVyeT1wYXN0ZShnZW9pbmZvX3F1ZXJ5LCIgV0hFUkUiLAogICAgICAgICAgICAgICAgICBwZXJpb2RfcXVlcnksIkFORCIsa2V5d29yZHNfcXVlcnksc2VwPSIiKQogICAgfQogIH0KICAjIE9idGFpbiBEYXRhCiBkYkdldFF1ZXJ5KGNvbm4scXVlcnkpCn0KYGBgCgoKIyMgR2V0IHRyZW5kIGZ1bmN0aW9uCmBgYHtyfQpnZXRUd2l0dGVyVHJlbmQ9ZnVuY3Rpb24oY29ubixnZW9pbmZvPSdjb3VudHJ5Jyx0cmVuZD0nZGF5JyxrZXl3b3Jkcz1OVUxMLAogICAgICAgICAgICAgICAgICAgICAgIHBlcmlvZD1jKCcyMDIwLTAzLTI5IDAwOjAwOjAwJywnMjAyMC0wNC0zMCAyMzo1OTo1OScpKXsKICAjIEFkZCB0cmVuZCBjY29uZGl0aW9ucyBhY2NvcmRpbmcgdG8gJ3RyZW5kJwogIGlmKHRyZW5kPT0nZGF5Jyl7CiAgICB0cmVuZF9xdWVyeT1jKCInJVktJW0tJWQnIiwiZGF0ZSIpCiAgfQogIGVsc2V7CiAgICBpZih0cmVuZD09J3dlZWsnKXsKICAgICAgdHJlbmRfcXVlcnk9YygiJyVXJyIsIndlZWsiKQogICAgfQogICAgZWxzZXsKICAgICAgaWYodHJlbmQ9PSdtb250aCcpewogICAgICAgIHRyZW5kX3F1ZXJ5PWMoIiclbSciLCJtb250aCIpCiAgICAgIH0KICAgICAgZWxzZXsKICAgICAgICBzdG9wKCJUaGUgdHJlbmQgY2FuIG9ubHkgYmUgJ2RheScsICd3ZWVrJyBvciAnbW9udGgnLiIpIAogICAgICB9CiAgICB9CiAgfQogICAgIyBTZWxlY3QgdGFibGUgb2YgZGF0YWJhc2UgYWNjb3JkaW5nIHRvICdnZW9pbmZvJwogIGlmKGlzLm51bGwoZ2VvaW5mbykpewogICAgZ2VvaW5mb19xdWVyeT1wYXN0ZSgiU0VMRUNUIHN0cmZ0aW1lKCIsdHJlbmRfcXVlcnlbMV0sCiAgICAgICAgICAgICAgICAgICAgICAgICIsY3JlYXRlZF9hdCkgQVMgIix0cmVuZF9xdWVyeVsyXSwiLCAiLAogICAgICAgICAgICAgICAgICAgICAgICAiY291bnQoKikgQVMgbnVtYmVyLCAiLAogICAgICAgICAgICAgICAgICAgICAgICAiYXZnKHNlbnRpbWVudF9zY29yZSkgQVMgc2VudGltZW50X3Njb3JlICIsCiAgICAgICAgICAgICAgICAgICAgICAgICJGUk9NIENvcm9uYXZpcnVzVHdlZXRzICIsCiAgICAgICAgICAgICAgICAgICAgICAgICJMRUZUIEpPSU4gVHdlZXRzU2VudGltZW50IE9OICIsCiAgICAgICAgICAgICAgICAgICAgICAgICJDb3JvbmF2aXJ1c1R3ZWV0cy5zdGF0dXNfaWQ9IiwKICAgICAgICAgICAgICAgICAgICAgICAgIlR3ZWV0c1NlbnRpbWVudC5zdGF0dXNfaWQiLHNlcD0iIikKICAgIGdyb3VwX3F1ZXJ5PXBhc3RlKCIgR1JPVVAgQlkgc3RyZnRpbWUoIix0cmVuZF9xdWVyeVsxXSwKICAgICAgICAgICAgICAgICAgICAgICIsY3JlYXRlZF9hdCkiLHNlcD0iIikKICB9CiAgZWxzZXsKICAgIGlmKGdlb2luZm89PSdjb3VudHJ5Jyl7CiAgICBnZW9pbmZvX3F1ZXJ5PXBhc3RlKCJTRUxFQ1Qgc3RyZnRpbWUoIix0cmVuZF9xdWVyeVsxXSwKICAgICAgICAgICAgICAgICAgICAgICAgIixjcmVhdGVkX2F0KSBBUyAiLHRyZW5kX3F1ZXJ5WzJdLCIsICIsCiAgICAgICAgICAgICAgICAgICAgICAgICJjb3VudCgqKSBBUyBudW1iZXIsIGNvdW50cnksICIsCiAgICAgICAgICAgICAgICAgICAgICAgICJhdmcoc2VudGltZW50X3Njb3JlKSBBUyBzZW50aW1lbnRfc2NvcmUgIiwKICAgICAgICAgICAgICAgICAgICAgICAgIkZST00gQ29yb25hdmlydXNUd2VldHNHZW8gIiwKICAgICAgICAgICAgICAgICAgICAgICAgIkxFRlQgSk9JTiBUd2VldHNHZW9TZW50aW1lbnQgT04gIiwKICAgICAgICAgICAgICAgICAgICAgICAgIkNvcm9uYXZpcnVzVHdlZXRzR2VvLnN0YXR1c19pZD0iLAogICAgICAgICAgICAgICAgICAgICAgICAiVHdlZXRzR2VvU2VudGltZW50LnN0YXR1c19pZCAiLAogICAgICAgICAgICAgICAgICAgICAgICAiTEVGVCBKT0lOIEdlb0RldGFpbCBPTiAiLAogICAgICAgICAgICAgICAgICAgICAgICAiQ29yb25hdmlydXNUd2VldHNHZW8ubGF0PUdlb0RldGFpbC5sYXQgIiwKICAgICAgICAgICAgICAgICAgICAgICAgIkFORCBDb3JvbmF2aXJ1c1R3ZWV0c0dlby5sbmc9R2VvRGV0YWlsLmxuZyIsc2VwPSIiKQogICAgZ3JvdXBfcXVlcnk9cGFzdGUoIiBHUk9VUCBCWSBzdHJmdGltZSgiLHRyZW5kX3F1ZXJ5WzFdLAogICAgICAgICAgICAgICAgICAgICAgIixjcmVhdGVkX2F0KSxjb3VudHJ5IixzZXA9IiIpCiAgICB9CiAgICBlbHNlewogICAgICBpZihnZW9pbmZvPT0nc3RhdGUnKXsKICAgICAgICBnZW9pbmZvX3F1ZXJ5PXBhc3RlKCJTRUxFQ1Qgc3RyZnRpbWUoIix0cmVuZF9xdWVyeVsxXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICIsY3JlYXRlZF9hdCkgQVMgIix0cmVuZF9xdWVyeVsyXSwiLCAiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgImNvdW50KCopIEFTIG51bWJlciwgY291bnRyeSwgc3RhdGUsICIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAiYXZnKHNlbnRpbWVudF9zY29yZSkgQVMgc2VudGltZW50X3Njb3JlICIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAiRlJPTSBDb3JvbmF2aXJ1c1R3ZWV0c0dlbyAiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIkxFRlQgSk9JTiBUd2VldHNHZW9TZW50aW1lbnQgT04gIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICJDb3JvbmF2aXJ1c1R3ZWV0c0dlby5zdGF0dXNfaWQ9IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICJUd2VldHNHZW9TZW50aW1lbnQuc3RhdHVzX2lkICIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAiTEVGVCBKT0lOIEdlb0RldGFpbCBPTiAiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIkNvcm9uYXZpcnVzVHdlZXRzR2VvLmxhdD1HZW9EZXRhaWwubGF0ICIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQU5EIENvcm9uYXZpcnVzVHdlZXRzR2VvLmxuZz1HZW9EZXRhaWwubG5nIixzZXA9IiIpCiAgICAgICAgZ3JvdXBfcXVlcnk9cGFzdGUoIiBHUk9VUCBCWSBzdHJmdGltZSgiLHRyZW5kX3F1ZXJ5WzFdLAogICAgICAgICAgICAgICAgICAgICAgICAgICIsY3JlYXRlZF9hdCksY291bnRyeSxzdGF0ZSIsc2VwPSIiKQogICAgICB9CiAgICAgIGVsc2V7CiAgICAgICAgaWYoZ2VvaW5mbz09J2NpdHknKXsKICAgICAgICAgIGdlb2luZm9fcXVlcnk9cGFzdGUoIlNFTEVDVCBzdHJmdGltZSgiLHRyZW5kX3F1ZXJ5WzFdLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiLGNyZWF0ZWRfYXQpIEFTICIsdHJlbmRfcXVlcnlbMl0sIiwgIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImNvdW50KCopIEFTIG51bWJlciwgY291bnRyeSwgc3RhdGUsIGNpdHksICIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJhdmcoc2VudGltZW50X3Njb3JlKSBBUyBzZW50aW1lbnRfc2NvcmUgIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkZST00gQ29yb25hdmlydXNUd2VldHNHZW8gIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkxFRlQgSk9JTiBUd2VldHNHZW9TZW50aW1lbnQgT04gIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkNvcm9uYXZpcnVzVHdlZXRzR2VvLnN0YXR1c19pZD0iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiVHdlZXRzR2VvU2VudGltZW50LnN0YXR1c19pZCAiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiTEVGVCBKT0lOIEdlb0RldGFpbCBPTiAiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQ29yb25hdmlydXNUd2VldHNHZW8ubGF0PUdlb0RldGFpbC5sYXQgIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkFORCBDb3JvbmF2aXJ1c1R3ZWV0c0dlby5sbmc9R2VvRGV0YWlsLmxuZyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlcD0iIikKICAgICAgICAgICBncm91cF9xdWVyeT1wYXN0ZSgiIEdST1VQIEJZIHN0cmZ0aW1lKCIsdHJlbmRfcXVlcnlbMV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIixjcmVhdGVkX2F0KSxjb3VudHJ5LHN0YXRlLGNpdHkiLHNlcD0iIikKICAgICAgICB9CiAgICAgICAgZWxzZXsKICAgICAgICAgIHN0b3AoIlRoZSBnZW9pbmZvIGNhbiBvbmx5IGJlICdOVUxMJywgJ2NpdHknLCAnc3RhdGUnIG9yICdjb3VudHJ5Jy4iKQogICAgICAgIH0KICAgICAgfQogICAgfQogIH0KICAKICAjIEFkZCBrZXl3b3JkcyBjb25kaXRpb25zIGFjY29yZGluZyB0byAna2V5d29yZHMnIAogIGlmKGlzLm51bGwoa2V5d29yZHMpKXsKICAgIGtleXdvcmRzX3F1ZXJ5PScnCiAgfQogIGVsc2V7CiAgICBmb3IoaSBpbiAxOmxlbmd0aChrZXl3b3JkcykpewogICAgICBpZihpPT0xKXsKICAgICAgICBrZXl3b3Jkc19xdWVyeT1wYXN0ZSgiICgodGV4dCBMSUtFICclIixrZXl3b3Jkc1tpXSwiJScpIixzZXA9IiIpCiAgICAgIH0KICAgICAgZWxzZXsKICAgICAgICBrZXl3b3Jkc19xdWVyeT1rZXl3b3Jkc19xdWVyeSU+JQogICAgICAgICAgcGFzdGUoIk9SICh0ZXh0IExJS0UgJyUiLGtleXdvcmRzW2ldLCIlJykiLHNlcD0iIikKICAgICAgfQogICAgfQogICAga2V5d29yZHNfcXVlcnk9cGFzdGUoa2V5d29yZHNfcXVlcnksIikgIixzZXA9IiIpCiAgfQogICMgQWRkIHBlcmlvZCBjb25kaXRpb25zIGFjY29yZGluZyB0byAncGVyaW9kJwogIGlmKGlzLm51bGwocGVyaW9kKSl7CiAgICBwZXJpb2RfcXVlcnk9JycKICB9CiAgZWxzZXsKICAgIGlmKGxlbmd0aChwZXJpb2QpPT0yKXsKICAgICAgICAgIHBlcmlvZF9xdWVyeT1wYXN0ZSgiIChzdHJmdGltZSgnJVktJW0tJWQgJUg6JU06JVMnLGNyZWF0ZWRfYXQpPj0iLAogICAgICAgICAgICAgICAgICAgICAgICJzdHJmdGltZSgnJVktJW0tJWQgJUg6JU06JVMnLCciLHBlcmlvZFsxXSwiJykgIiwKICAgICAgICAgICAgICAgICAgICAgICAiQU5EIHN0cmZ0aW1lKCclWS0lbS0lZCAlSDolTTolUycsY3JlYXRlZF9hdCk8PSIsCiAgICAgICAgICAgICAgICAgICAgICAgInN0cmZ0aW1lKCclWS0lbS0lZCAlSDolTTolUycsJyIscGVyaW9kWzJdLCInKSkgIiwKICAgICAgICAgICAgICAgICAgICAgICBzZXA9IiIpCiAgICB9CiAgICBlbHNlewogICAgICBzdG9wKCJUaGUgdGltZSBwZXJpb2Qgc2hvdWxkIGJlIGEgdmVjdG9yIHdpdGggbGVuZ3RoIDIuIikgCiAgICB9CiAgfQogICMgV3JpdGUgU1FMCiAgaWYocGVyaW9kX3F1ZXJ5PT0nJyl7CiAgICBpZihrZXl3b3Jkc19xdWVyeT09JycpewogICAgICBxdWVyeT1wYXN0ZShnZW9pbmZvX3F1ZXJ5LGdyb3VwX3F1ZXJ5LHNlcD0iIikKICAgIH0KICAgIGVsc2V7CiAgICAgIHF1ZXJ5PXBhc3RlKGdlb2luZm9fcXVlcnksIiBXSEVSRSIsa2V5d29yZHNfcXVlcnksZ3JvdXBfcXVlcnksc2VwPSIiKQogICAgfQogIH0KICBlbHNlewogICAgaWYoa2V5d29yZHNfcXVlcnk9PScnKXsKICAgICAgcXVlcnk9cGFzdGUoZ2VvaW5mb19xdWVyeSwiIFdIRVJFIixwZXJpb2RfcXVlcnksZ3JvdXBfcXVlcnksc2VwPSIiKQogICAgfQogICAgZWxzZXsKICAgICAgcXVlcnk9cGFzdGUoZ2VvaW5mb19xdWVyeSwiIFdIRVJFIixwZXJpb2RfcXVlcnksIkFORCIsa2V5d29yZHNfcXVlcnksCiAgICAgICAgICAgICAgICAgIGdyb3VwX3F1ZXJ5LHNlcD0iIikKICAgIH0KICB9CiAgIyBPYnRhaW4gRGF0YQogZGJHZXRRdWVyeShjb25uLHF1ZXJ5KQp9CmBgYAoKCiMjIEV4YW1wbGVzCmBgYHtyfQojIGNvbm5lY3QgdG8gZGF0YSBiYXNlCmNvbm49ZGJDb25uZWN0KFNRTGl0ZSgpLGRicGF0aCkKIyBnZXQgdHdpdHRlciBkYXRhIHdpdGggZ2VvIGluZm9ybWF0aW9uCnR3ZWV0c0dlbz1nZXRUd2l0dGVyRGF0YShjb25uLGdlb2luZm8gPSBULHBlcmlvZCA9IE5VTEwpCiMgZ2V0IHR3aXR0ZXIgbW9udGx5IGRhdGEgd2l0aCBnZW8gaW5mb3JtYXRpb24KdHdlZXRzTW9udGhseUdlbz1nZXRUd2l0dGVyVHJlbmQoY29ubixnZW9pbmZvID0gJ2NvdW50cnknLHRyZW5kPSdtb250aCcscGVyaW9kPU5VTEwpCiMgZ2V0IHR3aXR0ZXIgZGF0YSB3aXRoIGdpdmluZyBrZXl3b3Jkcwp0d2VldHM9Z2V0VHdpdHRlckRhdGEoY29ubixnZW9pbmZvID0gRixrZXl3b3JkcyA9IGMoJ21hc2snLCdOOTUnLCflj6PnvaknKSkKIyBnZXQgdHdpdHRlciBkYWlseSB0cmVuZHMgZ2l2aW5nIGtleXdvcmRzCnR3ZWV0c0RhaWx5PWdldFR3aXR0ZXJUcmVuZChjb25uLGdlb2luZm8gPSBOVUxMLGtleXdvcmRzID0gYygnbWFzaycsJ045NScsJ+WPo+e9qScpKQoKIyBkaXNjb25uZWN0IGRhdGEgYmFzZQpkYkRpc2Nvbm5lY3QoY29ubikKIyBSZWxlYXNlIG1lbW9yeQpybSh0d2VldHNHZW8sdHdlZXRzTW9udGhseUdlbyx0d2VldHMsdHdlZXRzRGFpbHkpCmdjKCkKYGBgCgojIFRoZSBzZW50aW1lbnQgYW5hbHlzaXMgcGFydApgYGB7cn0KIyBGb3IgRW5nbGlzaCB0d2VldHMgaW4gVW5pdGVkIFN0YXRlcwpnZXRFblNjb3JlIDwtIGZ1bmN0aW9uKERGLCBrZXl3b3JkKXsKICBERnN1YiA8LSBERiAlPiUKICAgIGZpbHRlcihsYW5nPT0iZW4iJmNvdW50cnlfY29kZT09IlVTIikKICAKICBERnN1YiRkYXRlIDwtIGdzdWIoIlQuKiIsICIiLCBERnN1YiRjcmVhdGVkX2F0KQogIAogIHdvcmRERiA8LSBERnN1YiAlPiUKICAgIHVubmVzdF90b2tlbnMod29yZCwgdGV4dCkgJT4lCiAgICBhbnRpX2pvaW4oc3RvcF93b3JkcykgCiAgCiAgc2NvcmVERiA8LSB3b3JkREYgJT4lCiAgICBpbm5lcl9qb2luKGdldF9zZW50aW1lbnRzKCJiaW5nIikpICU+JQogICAgY291bnQoc3RhdHVzX2lkLCBzZW50aW1lbnQpICU+JQogICAgc3ByZWFkKHNlbnRpbWVudCwgbikKICBzY29yZURGW2lzLm5hKHNjb3JlREYpXSA8LSAwCiAgc2NvcmVERiA8LSBzY29yZURGICU+JQogICAgbXV0YXRlKHNlbnRpbWVudF9zY29yZT0ocG9zaXRpdmUtbmVnYXRpdmUpLyhwb3NpdGl2ZStuZWdhdGl2ZSkpIAogIAogIHR3ZWV0U2NvcmVERiA8LSByaWdodF9qb2luKERGc3ViLCBzY29yZURGLCBieT0ic3RhdHVzX2lkIikKICAKICBpZihsZW5ndGgoa2V5d29yZCkhPTEpewogICAga2V5d29yZCA8LSBwYXN0ZShrZXl3b3JkLCBjb2xsYXBzZT0ifCIpCiAgfQogIGtleXdvcmRERiA8LSB0d2VldFNjb3JlREYgJT4lCiAgICBmaWx0ZXIoZ3JlcGwoa2V5d29yZCwgdGV4dCkpCiAgCiAgcmV0dXJuKAogICAga2V5d29yZERGICU+JQogICAgICBncm91cF9ieShkYXRlKSAlPiUKICAgICAgc3VtbWFyaXplKG92ZXJhbGxfc2VudGltZW50PW1lYW4oc2VudGltZW50X3Njb3JlKSkKICApCn0KCmBgYAoKYGBge3J9CiMgRm9yIGFsbCBsYW5ndWFnZXMKIyBDcmVhdGUgc2VudGltZW50IGxleGljb24gZGljdGlvbmFyeQojIGh0dHBzOi8vd3d3LmthZ2dsZS5jb20vcnRhdG1hbi9zZW50aW1lbnQtbGV4aWNvbnMtZm9yLTgxLWxhbmd1YWdlcwoKbGFuZ0NvZGUgPC0gcmVhZC5jc3YoIlNlbnRpbWVudExleGljb25zL2NvcnJlY3RlZE1ldGFkYXRhLmNzdiIsIGhlYWRlcj1UUlVFKSRgV2lraXBlZGlhLkxhbmd1YWdlLkNvZGVgCgpuZWdUZXJtcyA8LSBkYXRhX2ZyYW1lKGxhbmc9dmVjdG9yKCksIHdvcmQ9dmVjdG9yKCkpCnBvc1Rlcm1zIDwtIGRhdGFfZnJhbWUobGFuZz12ZWN0b3IoKSwgd29yZD12ZWN0b3IoKSkKCmZvcihpIGluIDE6bGVuZ3RoKGxhbmdDb2RlKSl7CiAgbmVnVGVybXMgPC0gcmJpbmQobmVnVGVybXMsIGRhdGFfZnJhbWUobGFuZz1sYW5nQ29kZVtpXSwgd29yZD1yZWFkLmRlbGltKGZpbGU9cGFzdGUwKCJTZW50aW1lbnRMZXhpY29ucy9uZWdhdGl2ZV93b3Jkc18iLCBsYW5nQ29kZVtpXSwgIi50eHQiLCBzZXA9IiIpLCBoZWFkZXI9RkFMU0UsIGNoZWNrLm5hbWVzID0gRkFMU0UpKSkKICBwb3NUZXJtcyA8LSByYmluZChwb3NUZXJtcywgZGF0YV9mcmFtZShsYW5nPWxhbmdDb2RlW2ldLCB3b3JkPXJlYWQuZGVsaW0oZmlsZT1wYXN0ZTAoIlNlbnRpbWVudExleGljb25zL3Bvc2l0aXZlX3dvcmRzXyIsIGxhbmdDb2RlW2ldLCAiLnR4dCIsIHNlcD0iIiksIGhlYWRlcj1GQUxTRSwgY2hlY2submFtZXMgPSBGQUxTRSkpKQp9Cm5lZ1Rlcm1zJHNlbnRpbWVudCA8LSAibmVnYXRpdmUiCnBvc1Rlcm1zJHNlbnRpbWVudCA8LSAicG9zaXRpdmUiCgpteVNlbnRpbWVudExleGljb24gPC0gYmluZF9yb3dzKG5lZ1Rlcm1zLCBwb3NUZXJtcykKbXlTZW50aW1lbnRMZXhpY29uIDwtIGFzLmRhdGEuZnJhbWUobXlTZW50aW1lbnRMZXhpY29uKQoKIyBjb2xuYW1lcyhteVNlbnRpbWVudExleGljb24pIDwtIGMoImxhbmciLCAid29yZCIsICJzZW50aW1lbnQiKQojIHJvd25hbWVzKG15U2VudGltZW50TGV4aWNvbikgPC0gMTpucm93KG15U2VudGltZW50TGV4aWNvbikKCiMgRnVuY3Rpb24KZ2V0U2NvcmUgPC0gZnVuY3Rpb24oREYsIHNlbGVjdGVkTGFuZywga2V5d29yZCl7CiAgREZzdWIgPC0gREYgJT4lCiAgICBmaWx0ZXIobGFuZz09c2VsZWN0ZWRMYW5nKQogIAogIERGc3ViJGRhdGUgPC0gZ3N1YigiVC4qIiwgIiIsIERGc3ViJGNyZWF0ZWRfYXQpCiAgCiAgd29yZERGIDwtIERGc3ViICU+JQogICAgdW5uZXN0X3Rva2Vucyh3b3JkLCB0ZXh0KSAlPiUKICAgICMgYW50aV9qb2luKGZyb21KU09OKGZpbGU9cGFzdGUwKCJzdG9wd29yZHMtanNvbi1tYXN0ZXIvZGlzdC8iLCBzZWxlY3RlZExhbmcsICIuanNvbiIsIHNlcD0iIikpKQogICAgYW50aV9qb2luKHN0b3B3b3JkcyhsYW5ndWFnZSA9IHNlbGVjdGVkTGFuZywgc291cmNlID0gInN0b3B3b3Jkcy1pc28iKSkKICAKICBzY29yZURGIDwtIHdvcmRERiAlPiUKICAgIGlubmVyX2pvaW4obXlTZW50aW1lbnRMZXhpY29uLCBieT1jKCJsYW5nIiwgIndvcmQiKSkgJT4lCiAgICBjb3VudChzdGF0dXNfaWQsIHNlbnRpbWVudCkgJT4lCiAgICBzcHJlYWQoc2VudGltZW50LCBuKQogIHNjb3JlREZbaXMubmEoc2NvcmVERildIDwtIDAKICBzY29yZURGIDwtIHNjb3JlREYgJT4lCiAgICBtdXRhdGUoc2VudGltZW50X3Njb3JlPShwb3NpdGl2ZS1uZWdhdGl2ZSkvKHBvc2l0aXZlK25lZ2F0aXZlKSkgCiAgCiAgdHdlZXRTY29yZURGIDwtIHJpZ2h0X2pvaW4oREZzdWIsIHNjb3JlREYsIGJ5PSJzdGF0dXNfaWQiKQogIAogIGlmKGxlbmd0aChrZXl3b3JkKSE9MSl7CiAgICBrZXl3b3JkIDwtIHBhc3RlKGtleXdvcmQsIGNvbGxhcHNlPSJ8IikKICB9CiAga2V5d29yZERGIDwtIHR3ZWV0U2NvcmVERiAlPiUKICAgIGZpbHRlcihncmVwbChrZXl3b3JkLCB0ZXh0KSkKICAKICByZXR1cm4oCiAgICBrZXl3b3JkREYgJT4lCiAgICAgIGdyb3VwX2J5KGRhdGUpICU+JQogICAgICBzdW1tYXJpemUob3ZlcmFsbF9zZW50aW1lbnQ9bWVhbihzZW50aW1lbnRfc2NvcmUpKQogICkKfQpgYGAKCgpWaXN1YWxpemF0aW9uCgpgYGB7cn0KCiMgRmlyc3RseSBtYWtlIGEgd29yZCBmcmVxdWVuY3kgcGxvdAojIGNvbm5lY3QgdG8gZGF0YSBiYXNlCmNvbm49ZGJDb25uZWN0KFNRTGl0ZSgpLGRicGF0aCkKIyBnZXQgdHdpdHRlciBkYXRhIHdpdGggZ2VvIGluZm9ybWF0aW9uCnR3ZWV0c0dlbz1nZXRUd2l0dGVyRGF0YShjb25uLHBlcmlvZCA9IE5VTEwpCiMgZ2V0IHR3aXR0ZXIgZGF0YSB3aXRoIGdpdmluZyBrZXl3b3JkcyBhbmQgdGltZQoKbWFzayA8LSBnZXRUd2l0dGVyVHJlbmQoY29ubixnZW9pbmZvID0gTlVMTCxrZXl3b3JkcyA9IGMoJ21hc2snLCdOOTUnKSkKbWFzayA8LSBtdXRhdGUobWFzaywKICAgICAgIHggPWMoMTozMykgKQoKIyBtYWtpbmcgYSB3b3JkIGZyZXF1ZW50IHBsb3Qgb2YgbWFzayByZWxhdGVkIGRhdGEuCmdncGxvdChkYXRhID0gbWFzaywgYWVzKHggPSB4LCB5ID0gbnVtYmVyKSkgKwogIGdlb21fYXJlYShjb2xvcj0iYmx1ZSIsZmlsbD0icHVycGxlIixhbHBoYT0uMikKCiMgbWFraW5nIGEgc2VudGltZW50IHNjb3JlIHBsb3Qgb2YgbWFzayByZWxhdGVkIGRhdGEuCmdncGxvdChkYXRhID0gbWFzaywgYWVzKHggPSB4LCB5ID0gc2VudGltZW50X3Njb3JlKSkgKwogIGdlb21fbGluZSgpKwogIGdlb21fcG9pbnQoc2l6ZT00LHNoYXBlPTIyLGNvbG9yPSJkYXJrcmVkIixmaWxsPSJwaW5rIikKIyBkaXNjb25uZWN0IGRhdGEgYmFzZQpkYkRpc2Nvbm5lY3QoY29ubikKCmBgYAoKYGBge3J9CgojIGxvYWQgc3ByZWFkIGRhdGEKZGFpbHkgPC0gcmVhZC5jc3YoZmlsZT0gIi9Vc2Vycy9tYWMvRGVza3RvcC9UcmluaXR5L3VzX2NvdmlkMTlfZGFpbHkuY3N2IiwgaGVhZGVyPVRSVUUpCnNwcmVhZF9kYXRhIDwtIHNlbGVjdChkYWlseSxkYXRlLGhvc3BpdGFsaXplZEN1bXVsYXRpdmUsZGVhdGgsZGVhdGhJbmNyZWFzZSxuZWdhdGl2ZUluY3JlYXNlLHBvc2l0aXZlSW5jcmVhc2UpJT4lCiAgbXV0YXRlKGRhdGVfbmV3PXltZChkYXRlKSklPiUKICBhcnJhbmdlKGRhaWx5LCBkZXNjKGRhdGVfbmV3KSkKc3ByZWFkX2RhdGEgPC0gc3ByZWFkX2RhdGFbNjg6MTAwLF0KCgojIHVzaW5nIHBsb3RseSBwYWNrYWdlIHRvIG1ha2UgYSBwbG90IHRoYXQgYm90aCBoYXZlIGRlYXRoLCBzZW50aW1lbnQgYW5kIGZyZXF1ZW5jeQpwb3NpdGl2ZWluY3JlYXNlIDwtIHNwcmVhZF9kYXRhWyw2XQpmcmVxdWVuY3kgPC0gbWFzayRudW1iZXIKc2VudGltZW50IDwtIG1hc2skc2VudGltZW50X3Njb3JlCmRhdGUgPC1zcHJlYWRfZGF0YSRkYXRlX25ldwpkYXRhIDwtIGRhdGEuZnJhbWUoZGF0ZSwgcG9zaXRpdmVpbmNyZWFzZSwgZnJlcXVlbmN5LCBzZW50aW1lbnQpCgpheSA8LSBsaXN0KAogIHRpY2tmb250ID0gbGlzdChjb2xvciA9ICJibHVlIiksCiAgb3ZlcmxheWluZyA9ICJ5IiwKICBzaWRlID0gInJpZ2h0IiwKICB0aXRsZSA9ICJ0aGUgZnJlcXVlbmN5IG9mICIKKQoKZmlnIDwtIHBsb3RfbHkoZGF0YSwgeCA9IH5kYXRlLCB5ID0gfnBvc2l0aXZlaW5jcmVhc2UsIG5hbWUgPSAncG9zaXRpdmVpbmNyZWFzZScsIHR5cGUgPSAnc2NhdHRlcicsIG1vZGUgPSAnbGluZXMrbWFya2VycycpIApmb3IoaSBpbiAxOjMyKQogIGlmKChzZW50aW1lbnRbaV0rc2VudGltZW50W2krMV0pLzIgPjAgKXsKICAgIGZpZyA8LWZpZyAlPiUgYWRkX3RyYWNlKHkgPSBmcmVxdWVuY3lbaTooaSsxKV0sIHg9ZGF0ZVtpOihpKzEpXSwgbmFtZSA9IE5VTEwsICBtb2RlID0gJ2xpbmVzJyx5YXhpcyA9ICJ5MiIsY29sb3IgPSBJKCJncmVlbiIpKQogIH1lbHNlewogICAgICAgICAgICAgIAogICAgZmlnIDwtZmlnICU+JSBhZGRfdHJhY2UoeSA9IGZyZXF1ZW5jeVtpOihpKzEpXSwgeD1kYXRlW2k6KGkrMSldLCBuYW1lID0gTlVMTCwgIG1vZGUgPSAnbGluZXMnLHlheGlzID0gInkyIixjb2xvciA9IEkoInJlZCIpKSAgICAgICAKICAgIH0KICAgICAgICAgICAgICAKZmlnIDwtIGZpZyAlPiUgbGF5b3V0KAogIHRpdGxlID0gIlN0YXRpc3RpY3MgYWJvdXQgJ21hc2ssIE45NSciLCB5YXhpczIgPSBheSwKICB4YXhpcyA9IGxpc3QodGl0bGU9IngiKSkKZmlnCgpgYGAKCiMgR2VvbSBwbG90cwoKYGBge3J9CgojIEZvciB0aGUgZ2VvIHBsb3QKI3N0YXRlcyA8LSBjKCJ0ZXhhcyIsIm9rbGFob21hIiwia2Fuc2FzIiwibG91aXNpYW5hIiwiYXJrYW5zYXMiLCJtaXNzb3VyaSIsImlvd2EiLAojIndpc2NvbnNpbiIsIm1pY2hpZ2FuIiwiaWxsaW5vaXMiLCJpbmRpYW5hIiwib2hpbyIsImtlbnR1Y2t5IiwidGVubmVzc2VlIiwKIyJhbGFiYW1hIiwibWlzc2lzc2lwcGkiLCJmbG9yaWRhIiwiZ2VvcmdpYSIsInNvdXRoIGNhcm9saW5hIiwibm9ydGggY2Fyb2xpbmEiLAojInZpcmdpbmlhIiwid2VzdCB2aXJnaW5pYSIsIm1hcnlsYW5kIiwiZGVsYXdhcmUiLCJwZW5uc3lsdmFuaWEiLCJuZXcgamVyc2V5IiwKIyJuZXcgeW9yayIsImNvbm5lY3RpY3V0IiwicmhvZGUgaXNsYW5kIiwibWFzc2FjaHVzZXR0cyIsInZlcm1vbnQiLAojIm5ldyBoYW1wc2hpcmUiLCJtYWluZSIpCgojdHVybiBkYXRhIGZyb20gdGhlIG1hcHMgcGFja2FnZSBpbiB0byBhIGRhdGEgZnJhbWUgc3VpdGFibGUgZm9yIHBsb3R0aW5nIHdpdGggZ2dwbG90MgojbWFwX3N0YXRlcyA8LSBtYXBfZGF0YSgiY291bnR5Iiwgc3RhdGVzKQojIFRvIGRyYXcgdGhlIGJvcmRlci1ieSBncm91cCAxMAojbWFwX3N0YXRlc19ib3JkZXIgPC0gbWFwX2RhdGEoInN0YXRlIixzdGF0ZXMpCnR3ZWV0c0dlbyA8LSB0d2VldHNHZW8gJT4lCiAgZ3JvdXBfYnkoc3RhdGUpJT4lCiAgbXV0YXRlKHN1bSA9IG4oKSwKICAgICAgICAgbG9uZz1sbmcpCgoKdHdlZXRzR2VvX0VuIDwtIGZpbHRlcih0d2VldHNHZW8sIGNvdW50cnkgPT0gJ1VuaXRlZCBTdGF0ZXMnKQpWaWV3KHR3ZWV0c0dlb19FbikKIyBzdW1tYXJ5KHR3ZWV0c0dlb19FbiRzdW0pCiMgZGl2aWRlIHN1bSBvZiB0d2VldHMgaW4gZWFjaCBzdGF0ZSBpbnRvIDQgcGFydHMgd2hpY2ggaXMgCiMgdHdlZXRzR2VvX0VuJGN1dCA8LSBjdXQodHdlZXRzR2VvX0VuJHN1bSwKIyAgICAgICAgICAgICAgICAgICAgICBicmVha3M9YygwLDE2NTgsNzg5MCwxNTA2NCwxODIwNiksCiAjICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZS5sb3dlc3QgPSBUKQoKdHdlZXRzR2VvX0VuXzEgPC0gdHdlZXRzR2VvX0VuJT4lCiAgbXV0YXRlKGxvbmc9YXMuZG91YmxlKGxvbmcpLAogICAgICAgICBsYXQ9YXMuZG91YmxlKGxhdCkpJT4lCiAgZ3JvdXBfYnkoc3RhdGUpJT4lCiAgc3VtbWFyaXplKHN1bV9zdGF0ZXM9bigpLCBtZWFuX3NlbnRpbWVudD1tZWFuKHNlbnRpbWVudF9zY29yZSkpCgoKCiMgTWFrZSB0aGUgZ2VvIHBsb3QgaW4gZ2dwbG90CiN0d2VldHNHZW9fcGxvdCA8LSBnZ3Bsb3QoKSsKIyAgZ2VvbV9wb2x5Z29uKHR3ZWV0c0dlb19Fbl8xLCBtYXBwaW5nPWFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1jaXR5KSwgZmlsbD1jdXQpKwogIyBjb25uZWN0cyB0aGUgb2JzZXJ2YXRpb25zIGluIHRoZSBvcmRlciBpbiB3aGljaCB0aGV5IGFwcGVhciBpbiB0aGUgJ21hcF9zdGF0ZXMnCiMgIGdlb21fcGF0aChtYXBfc3RhdGVzLCBtYXBwaW5nPWFlcyh4PWxvbmcsIHk9bGF0LGdyb3VwPWNpdHkpLGNvbG9yPSJncmV5IikrCiAjICBBZGQgdGhlIGJvcmRlciB0byBtYWtlIGl0IGNsZWFyLWJ5IEdyb3VwIDEwCiMgIGdlb21fcGF0aChtYXBfc3RhdGVzX2JvcmRlciwgbWFwcGluZz1hZXMoeD1sb25nLCB5PWxhdCxncm91cD1jaXR5KSxjb2xvcj0iYmxhY2siKSsKICAKICMgIGRpc3BsYXkgZGlzY3JldGUgdmFsdWVzIG9uIGEgbWFwCiMgIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGU9IkJsdWVzIikrCiAgIyBjaGFuZ2UgdGhlIG5hbWUgb2YgeCwgeSwgYW5kIHRpdGxlCiMgIHhsYWIoIkxvbmd0aXR1ZGUiKSt5bGFiKCJMYXRpdHVkZSIpK2dndGl0bGUoInR3ZWV0c0dlbyIpKwogIyAgYWRkIG1hcmtzCiMgIGxhYnMoZmlsbD0ibnVtYmVyIG9mIHR3ZWV0cyIpKwojICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41LCBzaXplID0gMTgpKQoKIyB0d2VldHNHZW9fcGxvdAoKCkFwcjQwIDwtIHJlYWQuY3N2KGZpbGU9ICIvVXNlcnMvbWFjL0Rlc2t0b3AvVHJpbml0eS91c19zdGF0ZXNfZGFpbHkuY3N2IiwgaGVhZGVyPVRSVUUpCkdlb3Bsb3QgPC0gbWVyZ2UoQXByNDAsdHdlZXRzR2VvX0VuXzEgLCBieT1jKCJzdGF0ZSIpKQpHZW9wbG90JGhvdmVyIDwtIHdpdGgoR2VvcGxvdCwgcGFzdGUoc3RhdGUsICc8YnI+JywgICc8YnI+JywgIlBvc2l0aXZlOiIsIHBvc2l0aXZlLCAiPGJyPiIsImRlYXRoOiIsIGRlYXRoLCI8YnI+IiwgIm51bWJlciBvZiB0d2VldHMiLCBzdW1fc3RhdGVzLCc8YnI+JywgInNlbnRpbWVudCBzY29yZSBvZiB0aGlzIHN0YXRlIiwgbWVhbl9zZW50aW1lbnQpKSAjcHV0IGRhdGEKCmcgPC0gbGlzdCgKICBzY29wZSA9ICd1c2EnLAogIHByb2plY3Rpb24gPSBsaXN0KHR5cGUgPSAnYWxiZXJzIHVzYScpLAogIHNob3dsYWtlcyA9IFRSVUUsCiAgbGFrZWNvbG9yID0gdG9SR0IoJ3doaXRlJykKKQpmaWcgPC0gcGxvdF9nZW8oR2VvcGxvdCwgbG9jYXRpb25tb2RlID0gJ1VTQS1zdGF0ZXMnKSAKZmlnIDwtIGZpZyAlPiUgYWRkX3RyYWNlKAogIGxvY2F0aW9ucyA9IH5zdGF0ZSwKICB0eXBlPSdjaG9yb3BsZXRoJywKICB6PSB+bWVhbl9zZW50aW1lbnQsCiAgdGV4dCA9IH5ob3ZlciwKICBjb2xvcnM9IlJlZHMiCikKIyBBZGQgYSB0aXRsZQpmaWcgPC0gZmlnICU+JSBsYXlvdXQodGl0bGUgPSAic2VudGltZW50IHNjb3JlIG9mIGVhY2ggc3RhdGUiLGdlbyA9IGcpCiMgRmluYWwKZmlnCgpgYGAKCmBgYHtyfQpnIDwtIGxpc3QoCiAgc2NvcGUgPSAndXNhJywKICBwcm9qZWN0aW9uID0gbGlzdCh0eXBlID0gJ2FsYmVycyB1c2EnKSwKICBzaG93bGFrZXMgPSBUUlVFLAogIGxha2Vjb2xvciA9IHRvUkdCKCd3aGl0ZScpKQpmaWcgPC0gcGxvdF9nZW8oR2VvcGxvdCwgbG9jYXRpb25tb2RlID0gJ1VTQS1zdGF0ZXMnKSAKZmlnIDwtIGZpZyAlPiUgYWRkX3RyYWNlKAogIGxvY2F0aW9ucyA9IH5zdGF0ZSwKICB0eXBlPSdjaG9yb3BsZXRoJywKICB6PSB+ZGVhdGgsCiAgdGV4dCA9IH5ob3ZlciwKICBjb2xvcnM9IlB1cnBsZXMiCikKIyBBZGQgYSB0aXRsZQpmaWcgPC0gZmlnICU+JSBsYXlvdXQodGl0bGUgPSAic2VudGltZW50IHNjb3JlIG9mIGVhY2ggc3RhdGUiLGdlbz1nKQojIEZpbmFsCmZpZwpgYGAKCgoKCgoK